package com.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import org.apache.log4j.Logger;
import org.primefaces.event.SelectEvent;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;
import org.springframework.web.jsf.FacesContextUtils;

import com.builder.MovimientoStockBuilder;
import com.builder.ProductoBuilder;
import com.builder.UsuarioBuilder;
import com.dao.BaseModelDAO;
import com.model.Deposito;
import com.model.MovimientoStock;
import com.model.Producto;
import com.model.Stock;
import com.model.Usuario;
import com.query.MovimientoStockQuery;
import com.type.EstadoEnum;
import com.type.TipoMovimientoManualEnum;
import com.type.TipoMovimientoStockEnum;
import com.util.JSFUtil;
import com.util.SpringUtil;
import com.util.ValidatorUtil;
import com.view.DepositoView;
import com.view.MovimientoStockView;
import com.view.ProductoView;
import com.view.UsuarioView;

@ManagedBean
@ViewScoped
@SuppressWarnings("serial")
public class MovimientoStockController extends
		PaginableController<MovimientoStock> {

	private Logger log = Logger.getLogger(MovimientoStockController.class);
	private BaseModelDAO<MovimientoStock> dao;
	private BaseModelDAO<Deposito> depositoDao;
	private BaseModelDAO<Usuario> usuarioDao;
	private BaseModelDAO<Producto> productoDao;
	private BaseModelDAO<Stock> stockDao;
	private MovimientoStock model;
	private MovimientoStockQuery query;
	private List<TipoMovimientoManualEnum> listaTipoMovimientoManual;
	private List<Deposito> listaDeposito;
	UsuarioView usuarioView;

	@ManagedProperty("#{movimientoStockView}")
	private MovimientoStockView view;

	@ManagedProperty("#{movimientoStockBuilder}")
	private MovimientoStockBuilder builder;

	@ManagedProperty("#{usuarioBuilder}")
	private UsuarioBuilder usuarioBuilder;

	@ManagedProperty("#{productoBuilder}")
	private ProductoBuilder productoBuilder;

	@SuppressWarnings("unchecked")
	public MovimientoStockController() {

		try {
			dao = (BaseModelDAO<MovimientoStock>) FacesContextUtils
					.getWebApplicationContext(FacesContext.getCurrentInstance())
					.getBean("movimientoStockDAO");
			depositoDao = (BaseModelDAO<Deposito>) FacesContextUtils
					.getWebApplicationContext(FacesContext.getCurrentInstance())
					.getBean("depositoDAO");
			usuarioDao = (BaseModelDAO<Usuario>) FacesContextUtils
					.getWebApplicationContext(FacesContext.getCurrentInstance())
					.getBean("usuarioDAO");
			productoDao = (BaseModelDAO<Producto>) FacesContextUtils
					.getWebApplicationContext(FacesContext.getCurrentInstance())
					.getBean("productoDAO");
			stockDao = (BaseModelDAO<Stock>) FacesContextUtils
					.getWebApplicationContext(FacesContext.getCurrentInstance())
					.getBean("stockDAO");

			query = new MovimientoStockQuery();
			listaTipoMovimientoManual = Arrays.asList(TipoMovimientoManualEnum
					.values());

			// cargo los depositos que puede ver el usuario
			listaDeposito = depositoDao.getList(SpringUtil
					.getPrincipalUserName());

			addEdit = false;

		} catch (Throwable e) {
			log.error(
					"Error al inicializar la clase MovimientoStockController",
					e);
			FacesContext.getCurrentInstance().addMessage(
					null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al realizar la operacion", ""));
		}
	}

	@PostConstruct
	private void inicializar() {
		usuarioView = new UsuarioView();
		Usuario unUsuario = usuarioDao.get(SpringUtil.getPrincipalUserName());
		usuarioView = usuarioBuilder.toView(unUsuario);
	}

	public MovimientoStock getModel() {
		return model;
	}

	public void setModel(MovimientoStock model) {
		this.model = model;
	}

	public MovimientoStockQuery getQuery() {
		return query;
	}

	public void setQuery(MovimientoStockQuery query) {
		this.query = query;
	}

	public MovimientoStockView getView() {
		return view;
	}

	public void setView(MovimientoStockView view) {
		this.view = view;
	}

	public MovimientoStockBuilder getBuilder() {
		return builder;
	}

	public void setBuilder(MovimientoStockBuilder builder) {
		this.builder = builder;
	}

	public List<TipoMovimientoManualEnum> getListaTipoMovimientoManual() {
		return listaTipoMovimientoManual;
	}

	public List<Deposito> getListaDeposito() {
		return listaDeposito;
	}

	public UsuarioBuilder getUsuarioBuilder() {
		return usuarioBuilder;
	}

	public void setUsuarioBuilder(UsuarioBuilder usuarioBuilder) {
		this.usuarioBuilder = usuarioBuilder;
	}

	public ProductoBuilder getProductoBuilder() {
		return productoBuilder;
	}

	public void setProductoBuilder(ProductoBuilder productoBuilder) {
		this.productoBuilder = productoBuilder;
	}

	@Override
	public void query(ActionEvent event) {
		loadList();
	}

	@Override
	public void edit(ActionEvent event) {

		try {
			model = lazyDM.getRowData();
			model = dao.findFULL(model.getID());
			view = builder.toView(model);
			addEdit = true;
		} catch (Throwable e) {
			log.error("Error al editar", e);
			FacesContext.getCurrentInstance().addMessage(
					null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al realizar la operacion", ""));
		}

	}

	@Override
	public void delete(ActionEvent event) {

		try {
			model = lazyDM.getRowData();
			dao.delete(model);
			loadList();
		} catch (Throwable e) {
			log.error("Error al eliminar", e);
			FacesContext.getCurrentInstance().addMessage(
					null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al realizar la operacion", ""));
		}
	}

	@Override
	public void add(ActionEvent event) {
		try {
			clear();
			addEdit = true;
		} catch (Throwable e) {
			JSFUtil.saveMessage(
					"No hay depósitos disponibles. Por favor agregue los depositos al sistema.",
					FacesMessage.SEVERITY_ERROR);
		}
	}

	private void saveModel() {

		model = builder.toDomain(view);

		// creo un stock o busco el stock
		try {

			// TODO PR por ahora no debe buscar por deposito
			// StockQuery stockQuery = new StockQuery(null, null, null, view
			// .getDepositoDestino().getId(), view.getProducto().getId());
			// List<Stock> listaStock = stockDao.getList(stockQuery);

			// obtengo el depósito
			Deposito deposito = depositoDao.find(model.getDepositoDestino()
					.getID());

			Stock stock;
			if (deposito.getCliente() != null) {
				stock = stockDao.findBy(deposito.getCliente().getID(), view
						.getProducto().getId(), null);
			} else {
				stock = stockDao.findBy(null, view.getProducto().getId(), null);
			}

			if (stock != null) {
				// stock = listaStock.get(0);
				// TODO PR ver porque esto tiene que estar en lazy sino trae
				// todos los movimientos asociados y pueden ser muchos
				// stock = stockDao.findFULL(stock.getID());
				model.setStock(stock);
				stock.getListaMovimientoStock().add(model);
			} else {
				// movimientos
				List<MovimientoStock> listaMovimientoStock = new ArrayList<MovimientoStock>();

				stock = new Stock(null, null, model.getProducto(), 0d,
						new Date(), new Date(), null,
						model.getDepositoDestino(), deposito.getCliente());
				model.setStock(stock);
				listaMovimientoStock.add(model);
				stock.setListaMovimientoStock(listaMovimientoStock);
			}

			// si es traspaso
			if (isTraspaso()) {

				Deposito depositoOrigen = depositoDao.find(view
						.getDepositoOrigen().getId());

				Stock stockOrigen;
				if (depositoOrigen.getCliente() != null) {
					stockOrigen = stockDao.findBy(depositoOrigen.getCliente()
							.getID(), view.getProducto().getId(), null);
				} else {
					stockOrigen = stockDao.findBy(null, view.getProducto()
							.getId(), null);
				}

				// TODO PR por ahora no debe buscar por deposito
				// stockQuery = new StockQuery(null, null, null, view
				// .getDepositoOrigen().getId(), view.getProducto()
				// .getId());
				// listaStock = stockDao.getList(stockQuery);

				// if (listaStock.size() > 0) {
				// stockOrigen = listaStock.get(0);
				// }

				// comparo los stocks
				if (stockOrigen != null
						&& stockOrigen.getCantidad() >= view.getCantidad()) {
					stockOrigen.setCantidad(stockOrigen.getCantidad()
							- view.getCantidad());
					stock.setCantidad(stock.getCantidad() + view.getCantidad());
					stockDao.edit(stockOrigen);
					if (stock.getID() != null) {
						stockDao.edit(stock);
					} else {
						stockDao.save(stock);
					}
					JSFUtil.saveMessage("La operación se realizó con exito",
							FacesMessage.SEVERITY_INFO);

					clear();
					if (!addEdit) {
						loadList();
					}
				} else {
					JSFUtil.saveMessage(
							"El stock del depósito de origen es insuficiente para realizar la operación",
							FacesMessage.SEVERITY_ERROR);
				}

			} else {
				if (view.getCantidad() > 0) {
					stock.setCantidad(stock.getCantidad() + view.getCantidad());
					if (stock.getID() != null) {
						stockDao.edit(stock);
					} else {
						stockDao.save(stock);
					}
					JSFUtil.saveMessage("La operación se realizó con exito",
							FacesMessage.SEVERITY_INFO);
					clear();
					if (!addEdit) {
						loadList();
					}
				} else if (view.getCantidad() < 0) {
					if (stock.getCantidad() >= (view.getCantidad() * -1)) {
						stock.setCantidad(stock.getCantidad()
								+ view.getCantidad());
						if (stock.getID() != null) {
							stockDao.edit(stock);
						} else {
							stockDao.save(stock);
						}
						JSFUtil.saveMessage(
								"La operación se realizó con exito",
								FacesMessage.SEVERITY_INFO);
						clear();
						if (!addEdit) {
							loadList();
						}
					} else {
						JSFUtil.saveMessage(
								"La cantidad a ajustar no puede dejar el stock negativo",
								FacesMessage.SEVERITY_ERROR);
					}
				} else {
					// cantidad 0
					JSFUtil.saveMessage("La cantidad no puede ser cero",
							FacesMessage.SEVERITY_ERROR);
				}
			}
		} catch (Throwable t) {
			log.error("Error al guardar", t);
			JSFUtil.saveMessage("Error al realizar la operacion",
					FacesMessage.SEVERITY_ERROR);
		}
	}

	@Override
	public void save(ActionEvent event) {

		try {

			List<String> errores = new ValidatorUtil().validate(view);

			if (errores.size() == 0) {

				// veo si se seleccionó un producto
				if (view.getProducto() != null) {

					// si es traspaso valido el depósito de origen
					if (isTraspaso()) {
						// veo si seleccinó un deposito de origen
						if (view.getDepositoOrigen() == null
								|| view.getDepositoOrigen().getId() == null) {
							JSFUtil.saveMessage(
									"Debe seleccionar un depósito de origen",
									FacesMessage.SEVERITY_ERROR);
						} else if (view.getDepositoOrigen().getId().intValue() == view
								.getDepositoDestino().getId().intValue()) {
							JSFUtil.saveMessage(
									"El depósito de origen y destino no pueden ser iguales",
									FacesMessage.SEVERITY_ERROR);
						} else if (view.getCantidad() <= 0) {
							JSFUtil.saveMessage(
									"Para un traspaso la cantidad debe ser mayor a cero",
									FacesMessage.SEVERITY_ERROR);
						} else {
							saveModel();
						}
					} else {
						saveModel();
					}

				} else {
					JSFUtil.saveMessage("Debe seleccionar un producto",
							FacesMessage.SEVERITY_ERROR);
				}
			} else {
				JSFUtil.saveMessages(errores);
			}
		} catch (Throwable e) {
			log.error("Error al guardar", e);
			FacesContext.getCurrentInstance().addMessage(
					null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al realizar la operacion", ""));
		}
	}

	@Override
	public void cancel(ActionEvent event) {
		addEdit = false;
		lazyDM = null;
		clear();
	}

	@Override
	public void clear() {

		try {
			DepositoView depositoDestino = new DepositoView();
			depositoDestino.setId(listaDeposito.get(0).getID());

			view = new MovimientoStockView(null,
					TipoMovimientoStockEnum.MANUAL,
					TipoMovimientoManualEnum.AJUSTE, null, 1D, null,
					new DepositoView(), EstadoEnum.AUTORIZADO, "", new Date(),
					usuarioView, null);
			model = new MovimientoStock();
		} catch (Throwable e) {
			throw e;
		}
	}

	private void loadList() {

		lazyDM = new LazyDataModel<MovimientoStock>() {
			@Override
			public List<MovimientoStock> load(int first, int pageSize,
					String sortField, SortOrder sortOrder,
					Map<String, Object> filters) {

				Map<String, Object> filtro = new HashMap<String, Object>();
				filtro.put("fecha", query.getFecha());
				filtro.put("tipoMovimientoStock",
						query.getTipoMovimientoStock());
				filtro.put("tipoMovimientoManual",
						query.getTipoMovimientoManual());
				filtro.put("depositoDestino", listaDeposito);

				return dao
						.getList(first, pageSize, "fecha", true, filtro, true);
			}
		};

		Map<String, Object> filtro = new HashMap<String, Object>();
		filtro.put("fecha", query.getFecha());
		filtro.put("tipoMovimientoStock", query.getTipoMovimientoStock());
		filtro.put("tipoMovimientoManual", query.getTipoMovimientoManual());
		filtro.put("depositoDestino", listaDeposito);
		lazyDM.setRowCount(dao.count(filtro, true).intValue());
	}

	public boolean isTraspaso() {
		return TipoMovimientoManualEnum.TRASPASO.equals(view
				.getTipoMovimientoManual());
	}

	public void tipoMovimientoManualListener() {
		if (isTraspaso()) {
			DepositoView depositoOrigen = new DepositoView();
			depositoOrigen.setId(listaDeposito.get(0).getID());
			view.setDepositoOrigen(depositoOrigen);
		} else {
			view.setDepositoOrigen(null);
		}
	}

	public List<ProductoView> completeProducto(String query) {
		List<ProductoView> listaProducto = null;
		try {

			List<Producto> listaProductosEntity = productoDao.getList(query);
			if (listaProductosEntity != null && listaProductosEntity.size() > 0) {
				listaProducto = productoBuilder.toView(listaProductosEntity);
			}

		} catch (Throwable e) {
			log.error("Error en le metodo completeProducto: ", e);
			FacesContext.getCurrentInstance().addMessage(
					null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al realizar la operacion", ""));
		}
		return listaProducto;
	}

	public boolean isProductoSelected() {
		return (view.getProducto() != null && view.getProducto().getId() != null);
	}

	public void deselecionarProducto(ActionEvent event) {
		view.setProducto(null);
	}

	public void productoSelectChange(SelectEvent event) {
		view.setProducto((ProductoView) event.getObject());
		System.out.println("se eligio una un producto "
				+ view.getProducto().getDetalle());
	}

}
